/*
 * $Id: QELogModel.m 248 2008-09-08 15:31:38Z jon $
 * Sleeper
 *
 * Created by Jon Gordon on 7/28/08
 * Copyright Jon Gordon.  All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining 
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including 
 * without limitation the rights to use, copy, modify, merge, publish, 
 * distribute, sublicense, and/or sell copies of the Software, and to 
 * permit persons to whom the Software is furnished to do so, subject to 
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */


#import "QELogModel.h"

#import "QEEntryData.h"
#import "QENSStringExtension.h"


static const unsigned int CALLSIGN_FIELD_LENGTH = 10;
static const float MAX_CHARS_PER_SOAPBOX_LINE = 70.0;

/*
 * Statically scoped function, implementing a comparator so that a log can
 * be sorted in order of the serialSent field.
 */
static int contactIdxComparator(QEContact *c1, QEContact *c2, void *context)
{
    int v1 = [[c1 serialSent] intValue];
    int v2 = [[c2 serialSent] intValue];
    if (v1 < v2) {
        return NSOrderedAscending;
    } else if (v1 > v2) {
        return NSOrderedDescending;
    } else {
        return NSOrderedSame;
    } // else
} // contactIdxComparator((QEContact *), (QEContact *) (void *))


@interface QELogModel (private)
+ (NSArray *)wrappedSoapboxText:(NSString *)sourceText;

- (QEExchangeResolver *)resolver;
- (void)setResolver:(QEExchangeResolver *)newValue;

- (void)setExchangeValid:(BOOL)newValue;
@end


@implementation QELogModel
#pragma mark CREATION/DESTRUCTION
+ (void)initialize
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    static BOOL initialized = NO;
    
    if (!initialized) {
        NSLog(@"  initializing QELogModel");
        initialized = YES;
        
        // notify the runtime of key dependencies
        [self setKeys:[NSArray arrayWithObjects:@"contacts", nil]
triggerChangeNotificationsForDependentKey:@"qsos"];
        [self setKeys:[NSArray arrayWithObjects:@"contacts", nil]
triggerChangeNotificationsForDependentKey:@"sections"];
        [self setKeys:[NSArray arrayWithObjects:@"qsos", @"sections", nil]
triggerChangeNotificationsForDependentKey:@"score"];
    } // if
} // initialize


- (id)init
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    self = [super init];
    if (self) {
        [self setResolver:[[[QEExchangeResolver alloc] init] autorelease]];
        [self setContacts:[[[NSArray alloc] init] autorelease]];
        [self setEntryData:[[[QEEntryData alloc] init] autorelease]];
        [self setExtension:nil];
    } // if
    
    return self;
} // init


- (void)dealloc
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [self setResolver:nil];
    [self setContacts:nil];
    [self setEntryData:nil];
    [self setExtension:nil];
    
    [super dealloc];
} // dealloc


// Supports Cocoa's serialize/deserialize function
- (id)initWithCoder:(NSCoder *)coder
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    self = [self init];
    
    [self setContacts:[coder decodeObjectForKey:@"LMContacts"]];
    id newEntryData = [coder decodeObjectForKey:@"LMEntryData"];
    if (nil == newEntryData) {
        newEntryData = [[[QEEntryData alloc] init] autorelease];
    } // if
    [self setEntryData:newEntryData];
    [self setExtension:[coder decodeObjectForKey:@"LMExtension"]];
    
    return self;
} // initWithCoder:


// Supports Cocoa's serialize/deserialize function
- (void)encodeWithCoder:(NSCoder *)coder
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [coder encodeObject:[self contacts] forKey:@"LMContacts"];
    [coder encodeObject:[self entryData] forKey:@"LMEntryData"];
    [coder encodeObject:[self extension] forKey:@"LMExtension"];
} // encodeWithCoder:


#pragma mark MODEL OPERATIONS
- (QEContact *)addContact
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (![[self resolver] isExchangeValid]) {
        NSLog(@"*** WARNING: [QELogModel addContact] invoked for invalid exchange! ***");
        return nil;
    } // if
    
    QEContact *newContact = [[[QEContact alloc] init] autorelease];
    NSDictionary *contactFields = [[self resolver] resolvedElements];
    [newContact setValuesForKeysWithDictionary:contactFields];
    [newContact setSerialSent:[NSNumber numberWithInt:([[self contacts] count] + 1)]];
    [newContact setTime:[NSDate date]];
    
    [self willChangeValueForKey:@"contacts"];
    [contacts insertObject:newContact atIndex:0];
    [self didChangeValueForKey:@"contacts"];
    
    return newContact;
} // addContact


- (void)removeContact:(QEContact *)contact
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [self willChangeValueForKey:@"contacts"];
    [contacts removeObject:contact];
    [self didChangeValueForKey:@"contacts"];
} // removeContact:


- (BOOL)setExchangeRcvd:(NSString *)newValue
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [self setExchangeValid:[resolver resolveExchange:newValue]];
    return [self exchangeValid];
} // setExchangeRcvd:


- (BOOL)exchangeValid
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return exchangeValid;
} // exchangeValid


- (BOOL)isCallInLog:(NSString *)call
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSEnumerator *e = [contacts objectEnumerator];
    QEContact *contact;
    while (nil != (contact = [e nextObject])) {
        if ([call isEqual:[contact call]]) {
            return YES;
        } // if
    } // while
    
    return NO;
} // isCallInLog:


- (NSString *)dataAsCabrilloText
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    QEEntryData *ed = [self entryData];
    
    NSTimeZone *tzUTC = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
    NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
    [formatter setFormatterBehavior:NSDateFormatterBehavior10_4];
    [formatter setTimeZone:tzUTC];
    [formatter setDateFormat:@"yyyy-MM-dd HHmm"];
    
    NSString *contestName = nil;
    if (nil == [ed modeName]) {
        contestName = @"";
    } else {
        contestName = [NSString stringWithFormat:@"ARRL-SS-%@", [ed modeName]];
    } // else
    
    NSString *assistedText = nil;
    if ([[ed assisted] boolValue]) {
        assistedText = @"ASSISTED";
    } else {
        assistedText = @"NON-ASSISTED";
    } // else
    
    NSString *operatorText = nil;
    if ((nil != [ed additionalOperators]) && ([[ed additionalOperators] count] > 0)) {
        operatorText = @"MULTI-OP";
    } else {
        operatorText = @"SINGLE-OP";
    } // else
    
    NSString *powerText = nil;
    if ([[ed outputPower] floatValue] > 100.0) {
        powerText = @"HIGH";
    } else if ([[ed outputPower] floatValue] > 5.0) {
        powerText = @"LOW";
    } else {
        powerText = @"QRP";
    } // else
    
    NSString *transmitterText = @"ONE"; // SS rules bar multiple transmitters
    NSString *operatorsListText = [[ed additionalOperators] componentsJoinedByString:@" "];
    NSArray *soapboxLines = [QELogModel wrappedSoapboxText:[ed soapboxText]];
    
    NSString *nextData;
    
    NSMutableString *data = [[NSMutableString alloc] init];
    [data appendString:@"START-OF-LOG: 3.0\n"];
    [data appendFormat:@"CONTEST: %@\n", contestName];
    [data appendFormat:@"CALLSIGN: %@\n", [ed entryCallsign]];
    [data appendFormat:@"CATEGORY-ASSISTED: %@\n", assistedText];
    [data appendFormat:@"CATEGORY-OPERATOR: %@\n", operatorText];
    [data appendFormat:@"CATEGORY-POWER: %@\n", powerText];
    [data appendFormat:@"LOCATION: %@\n", [ed section]];
    [data appendFormat:@"CATEGORY-TRANSMITTER: %@\n", transmitterText];
    [data appendFormat:@"CLAIMED-SCORE: %d\n", [[self score] intValue]];
    
    if ((nil != [ed additionalOperators]) && ([[ed additionalOperators] count] > 0)) {
        [data appendFormat:@"OPERATORS: %@\n", operatorsListText];
    } // if
    
    if ([[ed clubEntry] boolValue]) {
        [data appendFormat:@"CLUB: %@\n", [ed clubName]];
    } // if
    
    [data appendFormat:@"NAME: %@\n", [ed name]];
    
    nextData = [ed address1];
    if ((nil != nextData) && ([nextData length] > 0)) {
        [data appendFormat:@"ADDRESS: %@\n", nextData];
    } // if
    nextData = [ed address2];
    if ((nil != nextData) && ([nextData length] > 0)) {
        [data appendFormat:@"ADDRESS: %@\n", nextData];
    } // if
    [data appendFormat:@"ADDRESS: %@, %@ %@\n",
     [ed city],
     [ed stateProv],
     [ed postCode]];
    [data appendFormat:@"ADDRESS: %@\n", [ed country]];
    
    if ((nil != soapboxLines) && ([soapboxLines count] > 0)) {
        NSEnumerator *linesEnumerator = [soapboxLines objectEnumerator];
        NSString *nextLine;
        while (nil != (nextLine = [linesEnumerator nextObject])) {
            [data appendFormat:@"SOAPBOX: %@\n", [nextLine chomp]];
        } // while
    } // if
    
    QEContact *nextContact;
    NSEnumerator *contactsEnumerator = [[[self contacts] sortedArrayUsingFunction:contactIdxComparator
                                                                          context:nil] objectEnumerator];
    NSString *qsoMode = nil;
    if (nil == [ed modeName]) {
        qsoMode = @"XX";
    } else if ([[ed modeName] isEqual:@"CW"]) {
        qsoMode = @"CW";
    } else if ([[ed modeName] isEqual:@"SSB"]) {
        qsoMode = @"PH";
    } else {
        // CAN'T HAPPEN
        NSLog(@"  WARNING: unrecognized mode name found in entry data");
        qsoMode = @"XX";
    } // else
    while (nil != (nextContact = [contactsEnumerator nextObject])) {
        [data appendFormat:@"QSO: %-5d %@ %@ %@ %4d %@ %@ %@ %@ %4d %@ %@ %@\n", 
         (int) ([[nextContact freq] doubleValue] * 1000.0),
         qsoMode,
         [formatter stringFromDate:[nextContact time]],
         [[ed entryCallsign] stringByPaddingToLength:CALLSIGN_FIELD_LENGTH
                                          withString:@" "
                                     startingAtIndex:0],
         [[nextContact serialSent] intValue],
         [ed category],
         [ed check],
         [ed section],
         [[nextContact call] stringByPaddingToLength:CALLSIGN_FIELD_LENGTH
                                          withString:@" "
                                     startingAtIndex:0],
         [[nextContact serialRcvd] intValue],
         [nextContact prec],
         [nextContact check],
         [nextContact section]];
    } // while
    
    [data appendString:@"END-OF-LOG:\n"];
    return [data autorelease];
} // dataAsCabrilloText


#pragma mark PUBLIC FIELDS
- (QEEntryData *)entryData
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[entryData retain] autorelease];
} // entryData

- (void)setEntryData:(QEEntryData *)newValue
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (entryData != newValue) {
        id tmp = entryData;
        entryData = [newValue copy];
        [tmp release];
    } // if
} // setEntryData:


- (id)extension
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[extension retain] autorelease];
} // extension

- (void)setExtension:(id)newValue
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (extension != newValue) {
        id tmp = extension;
        extension = [newValue copy];
        [tmp release];
    } // if
} // setExtension:


- (NSNumber *)qsos
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [NSNumber numberWithInt:[[self valueForKeyPath:@"contacts.@distinctUnionOfObjects.call"] count]];
} // qsos


- (NSNumber *)sections
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [NSNumber numberWithInt:[[self valueForKeyPath:@"contacts.@distinctUnionOfObjects.section"] count]];
} // sections


- (NSNumber *)score
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSNumber *qsos = [self qsos];
    NSNumber *sections = [self sections];
    return [NSNumber numberWithInt:(2 * [qsos intValue] * [sections intValue])];
} // score


- (int)nextSerial
{
    int result = [[self contacts] count];
    int maxSerial = [[self valueForKeyPath:@"contacts.@max.serialSent"] intValue];
    if (maxSerial > result) {
        result = maxSerial + 1;
    } else {
        ++result;
    } // else
    
    return result;
} // nextSerial


- (NSMutableArray *)contacts
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[contacts retain] autorelease];
} // contacts

- (void)setContacts:(NSMutableArray *)newValue
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (newValue != contacts) {
        id temp = contacts;
        if (nil != newValue) {
            contacts = [[NSMutableArray alloc] initWithArray:newValue];
        } else {
            contacts = nil;
        } // else
        [temp release];
    } // if
} // setContacts

- (NSDictionary *)allElements
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSMutableDictionary *result = 
    [NSMutableDictionary dictionaryWithDictionary:
     [[self resolver] resolvedElements]];
    
    NSEnumerator *namesEnum = [[QEExchangeResolver elementNames] objectEnumerator];
    NSString *name;
    while (nil != (name = [namesEnum nextObject])) {
        if (nil == [result objectForKey:name]) {
            [result setObject:[NSNull null] forKey:name];
        } // if
    } // while
    
    return [NSDictionary dictionaryWithDictionary:result];
} // allElements
@end


@implementation QELogModel (private)
+ (NSArray *)wrappedSoapboxText:(NSString *)sourceText
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (nil == sourceText) {
        return nil;
    } // if
    
    static NSTextView *wrapView = nil;
    NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
    
    /*
     * An NSTextView, undisplayed, is used to wrap the text to the specified
     * line size.  This might conceiveably fail to work for non-Roman
     * scripts, but, then, so will the systems that process Cabrillo-format
     * logs, so we're OK.
     */
    if (nil == wrapView) {
        NSFont *monospaceFont = [NSFont userFixedPitchFontOfSize:0.0];
        
        wrapView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 
                                                                0, 
                                                                ([monospaceFont maximumAdvancement].width * 
                                                                 MAX_CHARS_PER_SOAPBOX_LINE),
                                                                10)];
        [wrapView setRichText:NO];
        [wrapView setHorizontallyResizable:NO];
        [wrapView setVerticallyResizable:YES];
        [[wrapView textContainer] setLineFragmentPadding:0];
        [wrapView setFont:monospaceFont];
    } // if
    
    [wrapView setString:sourceText];
    NSLayoutManager *layoutManager = [wrapView layoutManager];
    unsigned int index = 0;
    unsigned int numberOfGlyphs = [layoutManager numberOfGlyphs];
    NSRange glyphRange, charRange;
    while (index < numberOfGlyphs) {
        (void) [layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&glyphRange];
        index = NSMaxRange(glyphRange);
        charRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:nil];
        [result addObject:[sourceText substringWithRange:charRange]];
    } // while
    
    return [NSArray arrayWithArray:result];
} // wrappedSoapboxText:


- (QEExchangeResolver *)resolver
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[resolver retain] autorelease];
} // resolver

- (void)setResolver:(QEExchangeResolver *)newValue
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (resolver != newValue) {
        id temp = resolver;
        resolver = [newValue retain];
        [temp release];
    } // if
} // setResolver:


- (void)setExchangeValid:(BOOL)newValue
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    exchangeValid = newValue;
} // setExchangeValid:
@end